/** * Copyright 2004-2016 Riccardo Solmi. All rights reserved. * This file is part of the Whole Platform. * * The Whole Platform is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Whole Platform is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>. */ package org.whole.lang.xsd.parsers; import static org.whole.lang.xml.util.XmlUtils.LANGUAGE_PATTERN; import static org.whole.lang.xml.util.XmlUtils.NAME_PATTERN; import static org.whole.lang.xml.util.XmlUtils.NCNAME_PATTERN; import static org.whole.lang.xml.util.XmlUtils.NMTOKEN_PATTERN; import static org.whole.lang.xml.util.XmlUtils.NORMALIZED_STRING_PATTERN; import static org.whole.lang.xml.util.XmlUtils.STRING_PATTERN; import static org.whole.lang.xml.util.XmlUtils.TOKEN_PATTERN; import static org.whole.lang.xml.util.XmlUtils.isDecimal; import static org.whole.lang.xml.util.XmlUtils.isQName; import static org.whole.lang.xml.util.XmlUtils.parseURI; import java.io.IOException; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.joda.time.Chronology; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Days; import org.joda.time.Interval; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import org.joda.time.Months; import org.joda.time.MutablePeriod; import org.joda.time.ReadableInterval; import org.joda.time.ReadablePartial; import org.joda.time.Years; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatterBuilder; import org.joda.time.format.DateTimeParser; import org.joda.time.format.DateTimeParserBucket; import org.joda.time.format.DateTimePrinter; import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.PeriodFormatter; import org.joda.time.format.PeriodFormatterBuilder; import org.whole.lang.parsers.FailureDataTypeParser; import org.whole.lang.parsers.IDataTypeParser; import org.whole.lang.reflect.EntityDescriptor; import org.whole.lang.util.Base64Utils; import org.whole.lang.xml.util.QName; /** * @author Enrico Persiani */ public class SchemaDataTypeParsers { private static IDataTypeParser unsignedByteDataTypeParser; public static IDataTypeParser unsignedByte() { if (unsignedByteDataTypeParser == null) { unsignedByteDataTypeParser = new FailureDataTypeParser() { public short parseShort(EntityDescriptor<?> ed, String value) { short unsignedByte = Short.parseShort(value); if (unsignedByte < 0 || unsignedByte > 255) throw new IllegalArgumentException("range error"); return unsignedByte; } public String unparseShort(EntityDescriptor<?> ed, short value) { return String.valueOf(value); } }; } return unsignedByteDataTypeParser; } private static IDataTypeParser unsignedShortDataTypeParser; public static IDataTypeParser unsignedShort() { if (unsignedShortDataTypeParser == null) { unsignedShortDataTypeParser = new FailureDataTypeParser() { public int parseInt(EntityDescriptor<?> ed, String value) { int unsignedShort = Integer.parseInt(value); if (unsignedShort < 0 || unsignedShort > 65535) throw new IllegalArgumentException("range error"); return unsignedShort; } public String unparseInt(EntityDescriptor<?> ed, int value) { return String.valueOf(value); } }; } return unsignedShortDataTypeParser; } private static IDataTypeParser unsignedIntDataTypeParser; public static IDataTypeParser unsignedInt() { if (unsignedIntDataTypeParser == null) { unsignedIntDataTypeParser = new FailureDataTypeParser() { public long parseLong(EntityDescriptor<?> ed, String value) { long unsignedInt = Long.parseLong(value); if (unsignedInt < 0 || unsignedInt > 4294967295L) throw new IllegalArgumentException("range error"); return unsignedInt; } public String unparseLong(EntityDescriptor<?> ed, long value) { return String.valueOf(value); } }; } return unsignedIntDataTypeParser; } private static IDataTypeParser decimalDataTypeParser; public static IDataTypeParser decimal() { if (decimalDataTypeParser == null) { decimalDataTypeParser = new FailureDataTypeParser() { public Object parseObject(EntityDescriptor<?> ed, String value) { if (!isDecimal(value)) throw new IllegalArgumentException("bad decimal format"); return new BigDecimal(value); } public String unparseObject(EntityDescriptor<?> ed, Object value) { String decimal = ((BigDecimal) value).toPlainString(); int index = decimal.length(); if (decimal.indexOf('.') != -1) while (decimal.charAt(index-1) == '0' && decimal.charAt(index-2) != '.') index--; return decimal.substring(0, index); } }; } return decimalDataTypeParser; } public static DateTimeFormatter createMillisFormatter() { return new DateTimeFormatterBuilder() .appendLiteral('.') .appendMillisOfSecond(3) .toFormatter(); } private static final int TZ_UPPER = DateTimeZone.forOffsetHoursMinutes(14, 0).getOffset(0L); private static final int TZ_LOWER = DateTimeZone.forOffsetHoursMinutes(-14, 00).getOffset(0L); private static DateTimeZone validate(DateTimeZone zone) { int zoneMillis = zone.getOffset(0L); if (zoneMillis > TZ_UPPER || zoneMillis < TZ_LOWER) throw new IllegalArgumentException("bad time zone format"); return zone; } private static DateTimeFormatter tzf; public static DateTimeFormatter timeZoneFormatter() { if (tzf == null) { tzf = new DateTimeFormatterBuilder() .appendTimeZoneOffset("Z", true, 2, 4) .toFormatter(); } return tzf; } private static DateTimeParser tp; private static DateTimeParser timeParser() { if (tp == null) { tp = new DateTimeFormatterBuilder() .appendHourOfDay(2) .appendLiteral(':') .appendMinuteOfHour(2) .appendLiteral(':') .appendSecondOfMinute(2) .appendOptional(createMillisFormatter().getParser()) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return tp; } private static DateTimePrinter tprt; private static DateTimePrinter timePrinter() { if (tprt == null) { tprt = new DateTimeFormatterBuilder() .appendHourOfDay(2) .appendLiteral(':') .appendMinuteOfHour(2) .appendLiteral(':') .appendSecondOfMinute(2) .append(MillisOfSecondDateTimePrinter.instance) .append(timeZoneFormatter().getPrinter()) .toPrinter(); } return tprt; } private static DateTimeFormatter time; public static DateTimeFormatter timeFormatter() { if (time == null) { time = new DateTimeFormatter(timePrinter(), timeParser()) .withZone(DateTimeZone.UTC); } return time; } private static IDataTypeParser timeDataTypeParser; public static IDataTypeParser time() { if (timeDataTypeParser == null) { timeDataTypeParser = new AbstractISO8601DataTypeParser(timeFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new DateTime(bucket.computeMillis(), validate(zone)); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalTime(bucket.computeMillis()); } }; } return timeDataTypeParser; } private static DateTimeParser dtp; private static DateTimeParser dateTimeParser() { if (dtp == null) { dtp = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.date()) .appendLiteral('T') .append(timeParser()) .toParser(); } return dtp; } private static DateTimePrinter dtprt; private static DateTimePrinter dateTimePrinter() { if (dtprt == null) { dtprt = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.date()) .appendLiteral('T') .append(timePrinter()) .toPrinter(); } return dtprt; } private static DateTimeFormatter dateTime; public static DateTimeFormatter dateTimeFormatter() { if (dateTime == null) { dateTime = new DateTimeFormatter(dateTimePrinter(), dateTimeParser()) .withZone(DateTimeZone.UTC); } return dateTime; } private static IDataTypeParser dateTimeDataTypeParser; public static IDataTypeParser dateTime() { if (dateTimeDataTypeParser == null) { dateTimeDataTypeParser = new AbstractISO8601DataTypeParser(dateTimeFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new DateTime(bucket.computeMillis(), validate(zone)); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDateTime(bucket.computeMillis()); } }; } return dateTimeDataTypeParser; } private static DateTimeParser dp; private static DateTimeParser dateParser() { if (dp == null) { dp = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.date()) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return dp; } private static DateTimePrinter dprt; private static DateTimePrinter datePrinter() { if (dprt == null) { dprt = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.date()) .append(timeZoneFormatter()) .toPrinter(); } return dprt; } private static DateTimeFormatter date; public static DateTimeFormatter dateFormatter() { if (date == null) { date = new DateTimeFormatter(datePrinter(), dateParser()) .withZone(DateTimeZone.UTC); } return date; } private static final int TZ_REC_UPPER_MILLIS = DateTimeZone.forOffsetHoursMinutes(12, 0).getOffset(0L); private static final int TZ_REC_LOWER_MILLIS = DateTimeZone.forOffsetHoursMinutes(-12, 00).getOffset(0L); private static final int H24 = TZ_REC_UPPER_MILLIS-TZ_REC_LOWER_MILLIS; private static IDataTypeParser dateDataTypeParser; public static IDataTypeParser date() { if (dateDataTypeParser == null) { dateDataTypeParser = new AbstractISO8601DataTypeParser(dateFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new Interval(new DateTime(bucket.computeMillis(), validate(zone)), Days.ONE); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDate(bucket.computeMillis()); } @Override public String unparseObject(EntityDescriptor<?> ed, Object value) { if (value instanceof ReadableInterval) { // make use of recoverable time zones DateTime start = ((ReadableInterval) value).getStart(); int millisOffset = start.getZone().getOffset(0L); DateTimeZone zone = DateTimeZone.forOffsetMillis( millisOffset > TZ_REC_UPPER_MILLIS ? millisOffset-H24 : (millisOffset <= TZ_REC_LOWER_MILLIS ? millisOffset+H24 : millisOffset)); return getFormatter().withZone(zone).print(start); } else return super.unparseObject(ed, value); } }; } return dateDataTypeParser; } private static PeriodFormatter duration; public static PeriodFormatter durationFormatter() { if (duration == null) { duration = new PeriodFormatterBuilder() .rejectSignedValues(true) .appendLiteral("P") .appendYears() .appendSuffix("Y") .appendMonths() .appendSuffix("M") .appendWeeks() .appendSuffix("W") .appendDays() .appendSuffix("D") .appendSeparatorIfFieldsAfter("T") .appendHours() .appendSuffix("H") .appendMinutes() .appendSuffix("M") .appendSecondsWithOptionalMillis() .appendSuffix("S") .toFormatter(); } return duration; } private static IDataTypeParser durationDataTypeParser; public static IDataTypeParser duration() { if (durationDataTypeParser == null) { durationDataTypeParser = new FailureDataTypeParser() { public Object parseObject(EntityDescriptor<?> ed, String value) { if (value.startsWith("-")) { MutablePeriod period = durationFormatter().parseMutablePeriod(value.substring(1)); for (int i=0, size=period.size(); i<size; i++) period.setValue(i, -period.getValue(i)); return period; } else return durationFormatter().parseMutablePeriod(value); } public String unparseObject(EntityDescriptor<?> ed, Object value) { if (value instanceof MutablePeriod) { MutablePeriod period = (MutablePeriod) value; int i=0; boolean isNegative = false; for (int size=period.size(); i<size; i++) { int n = period.getValue(i); if (n != 0) { isNegative = n < 0; break; } } if (isNegative) { for (int size=period.size(); i<size; i++) { int n = period.getValue(i); if (n>0) throw new IllegalArgumentException("bad duration format"); period.setValue(i, -n); } } return (isNegative ? "-" : "") + durationFormatter().print(period); } else return String.valueOf(value); } }; } return durationDataTypeParser; } private static DateTimeParser ymp; private static DateTimeParser yearMonthParser() { if (ymp == null) { ymp = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.yearMonth()) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return ymp; } private static DateTimePrinter ymprt; private static DateTimePrinter yearMonthPrinter() { if (ymprt == null) { ymprt = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.yearMonth()) .append(timeZoneFormatter()) .toPrinter(); } return ymprt; } private static DateTimeFormatter yearMonth; public static DateTimeFormatter yearMonthFormatter() { if (yearMonth == null) yearMonth = new DateTimeFormatter(yearMonthPrinter(), yearMonthParser()); return yearMonth; } private static IDataTypeParser yearMonthDataTypeParser; public static IDataTypeParser yearMonth() { if (yearMonthDataTypeParser == null) { yearMonthDataTypeParser = new AbstractISO8601DataTypeParser(yearMonthFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new Interval(new DateTime(bucket.computeMillis(), validate(zone)), Months.ONE); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDate(bucket.computeMillis()); } }; } return yearMonthDataTypeParser; } private static DateTimeParser yp; private static DateTimeParser yearParser() { if (yp == null) { yp = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.year()) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return yp; } private static DateTimePrinter yprt; private static DateTimePrinter yearPrinter() { if (yprt == null) { yprt = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.year()) .append(timeZoneFormatter()) .toPrinter(); } return yprt; } private static DateTimeFormatter year; public static DateTimeFormatter yearFormatter() { if (year == null) year = new DateTimeFormatter(yearPrinter(), yearParser()) .withOffsetParsed(); return year; } private static IDataTypeParser yearDataTypeParser; public static IDataTypeParser year() { if (yearDataTypeParser == null) { yearDataTypeParser = new AbstractISO8601DataTypeParser(yearFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new Interval(new DateTime(bucket.computeMillis(), validate(zone)), Years.ONE); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDate(bucket.computeMillis()); } }; } return yearDataTypeParser; } private static DateTimeParser mdp; private static DateTimeParser monthDayParser() { if (mdp == null) { mdp = new DateTimeFormatterBuilder() .appendLiteral("--") .appendMonthOfYear(2) .appendLiteral('-') .appendDayOfMonth(2) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return mdp; } private static DateTimePrinter mdprt; private static DateTimePrinter monthDayPrinter() { if (mdprt == null) { mdprt = new DateTimeFormatterBuilder() .appendLiteral("--") .appendMonthOfYear(2) .appendLiteral('-') .appendDayOfMonth(2) .append(timeZoneFormatter()) .toPrinter(); } return mdprt; } private static DateTimeFormatter monthDay; public static DateTimeFormatter monthDayFormatter() { if (monthDay == null) monthDay = new DateTimeFormatter(monthDayPrinter(), monthDayParser()); return monthDay; } private static IDataTypeParser monthDayDataTypeParser; public static IDataTypeParser monthDay() { if (monthDayDataTypeParser == null) { monthDayDataTypeParser = new AbstractISO8601DataTypeParser(monthDayFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new Interval(new DateTime(bucket.computeMillis(), validate(zone)), Days.ONE); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDate(bucket.computeMillis()); } }; } return monthDayDataTypeParser; } private static DateTimeParser mp; private static DateTimeParser monthParser() { if (mp == null) { mp = new DateTimeFormatterBuilder() .appendLiteral("--") .appendMonthOfYear(2) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return mp; } private static DateTimePrinter mprt; private static DateTimePrinter monthPrinter() { if (mprt == null) { mprt = new DateTimeFormatterBuilder() .appendLiteral("--") .appendMonthOfYear(2) .append(timeZoneFormatter()) .toPrinter(); } return mprt; } private static DateTimeFormatter month; public static DateTimeFormatter monthFormatter() { if (month == null) month = new DateTimeFormatter(monthPrinter(), monthParser()); return month; } private static IDataTypeParser monthDataTypeParser; public static IDataTypeParser month() { if (monthDataTypeParser == null) { monthDataTypeParser = new AbstractISO8601DataTypeParser(monthFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new Interval(new DateTime(bucket.computeMillis(), validate(zone)), Months.ONE); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDate(bucket.computeMillis()); } }; } return monthDataTypeParser; } private static DateTimeParser dyp; private static DateTimeParser dayParser() { if (dyp == null) { dyp = new DateTimeFormatterBuilder() .appendLiteral("---") .appendDayOfMonth(2) .appendOptional(new TimeZoneValidatingParser(timeZoneFormatter().getParser())) .toParser(); } return dyp; } private static DateTimePrinter dyprt; private static DateTimePrinter dayPrinter() { if (dyprt == null) { dyprt = new DateTimeFormatterBuilder() .appendLiteral("---") .appendDayOfMonth(2) .append(timeZoneFormatter()) .toPrinter(); } return dyprt; } private static DateTimeFormatter day; public static DateTimeFormatter dayFormatter() { if (day == null) day = new DateTimeFormatter(dayPrinter(), dayParser()); return day; } private static IDataTypeParser dayDataTypeParser; public static IDataTypeParser day() { if (dayDataTypeParser == null) { dayDataTypeParser = new AbstractISO8601DataTypeParser(dayFormatter()) { protected Object parseWithTimeZone(DateTimeParserBucket bucket) { DateTimeZone zone = DateTimeZone.forOffsetMillis(bucket.getOffset()); return new Interval(new DateTime(bucket.computeMillis(), validate(zone)), Days.ONE); } protected Object parseWithoutTimeZone(DateTimeParserBucket bucket) { return new LocalDate(bucket.computeMillis()); } }; } return dayDataTypeParser; } private static IDataTypeParser base64BinaryDataTypeParser; public static IDataTypeParser base64Binary() { if (base64BinaryDataTypeParser == null) { base64BinaryDataTypeParser = new FailureDataTypeParser() { public Object parseObject(EntityDescriptor<?> ed, String value) { return Base64Utils.decode(value); } public String unparseObject(EntityDescriptor<?> ed, Object value) { return Base64Utils.encode((byte[]) value); } }; } return base64BinaryDataTypeParser; } private static IDataTypeParser hexBinaryDataTypeParser; public static IDataTypeParser hexBinary() { if (hexBinaryDataTypeParser == null) { hexBinaryDataTypeParser = new FailureDataTypeParser() { public Object parseObject(EntityDescriptor<?> ed, String value) { if (value.length() % 2 != 0) throw new IllegalArgumentException("bad hexadecimal format"); return new BigInteger(value, 16).toByteArray(); } public String unparseObject(EntityDescriptor<?> ed, Object value) { return new BigInteger((byte[]) value).toString(16); } }; } return hexBinaryDataTypeParser; } private static IDataTypeParser anyURIDataTypeParser; public static IDataTypeParser anyURI() { if (anyURIDataTypeParser == null) { anyURIDataTypeParser = new FailureDataTypeParser() { public String parseString(EntityDescriptor<?> ed, String value) { parseURI(value); return value; } public String unparseString(EntityDescriptor<?> ed, String value) { return value; } }; } return anyURIDataTypeParser; } private static IDataTypeParser qnameDataTypeParser; public static IDataTypeParser qname() { if (qnameDataTypeParser == null) { qnameDataTypeParser = new FailureDataTypeParser() { public Object parseObject(EntityDescriptor<?> ed, String value) { if (!isQName(value)) throw new IllegalArgumentException("bad qname"); int index = value.indexOf(':'); return QName.create(null, value.substring(index+1), value.substring(0, index)); } public String unparseObject(EntityDescriptor<?> ed, Object value) { QName qname = (QName) value; return qname.getPrefix()+':'+qname.getLocalPart(); } }; } return qnameDataTypeParser; } private static IDataTypeParser stringDataTypeParser; public static IDataTypeParser string() { if (stringDataTypeParser == null) stringDataTypeParser = new PatternValidatingDataTypeParser(STRING_PATTERN); return stringDataTypeParser; } private static IDataTypeParser normalizedStringDataTypeParser; public static IDataTypeParser normalizedString() { if (normalizedStringDataTypeParser == null) normalizedStringDataTypeParser = new PatternValidatingDataTypeParser(NORMALIZED_STRING_PATTERN); return normalizedStringDataTypeParser; } private static IDataTypeParser tokenDataTypeParser; public static IDataTypeParser token() { if (tokenDataTypeParser == null) tokenDataTypeParser = new PatternValidatingDataTypeParser(TOKEN_PATTERN); return tokenDataTypeParser; } private static IDataTypeParser languageDataTypeParser; public static IDataTypeParser language() { if (languageDataTypeParser == null) languageDataTypeParser = new PatternValidatingDataTypeParser(LANGUAGE_PATTERN); return languageDataTypeParser; } private static IDataTypeParser nameDataTypeParser; public static IDataTypeParser name() { if (nameDataTypeParser == null) nameDataTypeParser = new PatternValidatingDataTypeParser(NAME_PATTERN); return nameDataTypeParser; } private static IDataTypeParser nmtokenDataTypeParser; public static IDataTypeParser nmtoken() { if (nmtokenDataTypeParser == null) nmtokenDataTypeParser = new PatternValidatingDataTypeParser(NMTOKEN_PATTERN); return nmtokenDataTypeParser; } private static IDataTypeParser ncnameDataTypeParser; public static IDataTypeParser ncname() { if (ncnameDataTypeParser == null) ncnameDataTypeParser = new PatternValidatingDataTypeParser(NCNAME_PATTERN); return ncnameDataTypeParser; } public static class PatternValidatingDataTypeParser extends FailureDataTypeParser { protected final Pattern pattern; public PatternValidatingDataTypeParser(Pattern pattern) { this.pattern = pattern; } @Override public String parseString(EntityDescriptor<?> ed, String value) { if (!pattern.matcher(value).matches()) throw new IllegalArgumentException("bad format"); return value; } @Override public String unparseString(EntityDescriptor<?> ed, String value) { return value; } } public static class TimeZoneValidatingParser implements DateTimeParser { private static final Pattern TZ_PATTERN = Pattern.compile("[-+](\\d\\d):(\\d\\d)"); protected DateTimeParser timeZoneParser; public TimeZoneValidatingParser(DateTimeParser timeZoneParser) { this.timeZoneParser = timeZoneParser; } public int estimateParsedLength() { return timeZoneParser.estimateParsedLength(); } public int parseInto(DateTimeParserBucket bucket, String text, int position) { int state = timeZoneParser.parseInto(bucket, text, position); Matcher matcher = TZ_PATTERN.matcher(text.subSequence(position, text.length())); if (matcher.matches() && Integer.valueOf(matcher.group(1)+matcher.group(2))>1400) throw new IllegalArgumentException("timezone out of range"); return state; } } public static class MillisOfSecondDateTimePrinter implements DateTimePrinter { public static final DateTimePrinter instance = new MillisOfSecondDateTimePrinter(); protected DateTimePrinter printer; protected MillisOfSecondDateTimePrinter() { printer = new DateTimeFormatterBuilder() .appendLiteral('.') .appendFractionOfSecond(0, 3) .toPrinter(); } public int estimatePrintedLength() { return printer.estimatePrintedLength(); } public void printTo(StringBuffer buf, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) { if (instant % 1000 != 0) printer.printTo(buf, instant, chrono, displayOffset, displayZone, locale); } public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { long instant = partial.getChronology().set(partial, 0L); if (instant % 1000 != 0) printer.printTo(buf, partial, locale); } public void printTo(Writer out, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { if (instant % 1000 != 0) printer.printTo(out, instant, chrono, displayOffset, displayZone, locale); } public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { long instant = partial.getChronology().set(partial, 0L); if (instant % 1000 != 0) printer.printTo(out, partial, locale); } } }